//
// Created by haoy on 2017/6/30.
//

#ifndef MERGE_BLOCK_HPP
#define MERGE_BLOCK_HPP

#include <cassert>
#include <iostream>
#include <memory>
#include <string>
#include <vector>

#include "dimension.hpp"
#include "track.hpp"

class block {
public:
    block(const std::string& name, const dimension& dim, const uint64_t duration_ms)
            : name(name), dim(dim), duration_ms(duration_ms) {}

    std::string filter(uint64_t base_begin_ts) const {
        return std::string(_filter(base_begin_ts));
    };

    std::string name;
    dimension dim;
    uint64_t duration_ms;
private:
    virtual std::string _filter(uint64_t base_begin_ts) const = 0;
};

//class source_block : public block {
//public:
//    source_block(const video_track& source, const dimension& dim, const std::string& name, const uint64_t offset_ms)
//            : block(name, dim), source(source), offset_ms(offset_ms) {}
//    // offset
//    // color=color=Black:size=320x180:d=5[v-0-0-offset];
//    // [v-0-0-offset][1:v]concat,scale=size=640x360fifo[v-0-0];
//private:
//    virtual std::string _filter() const {
//        std::ostringstream w;
//        if (offset_ms) {
//            w << "color=color=Black:size=" << dim.w << "x" << dim.h;
//            w << ":d=" << (offset_ms / 1000) << "." << (offset_ms % 1000);
//            w << ",fifo";
//            w << "[" << name << "-offset];";
//        }
//        w << "[" << source.input_id << ":" << source.id << "]";
//        w << "scale=size=" << dim.w << "x" << dim.h;
//        w << ",fifo";
//        w << "[" << name << "-scaled];";
//        if (offset_ms) {
//            w << "[" << name << "-offset]";
//            w << "[" << name << "-scaled]";
//            w << "concat,fifo";
//        } else {
//            w << "[" << name << "-scaled]" << "fifo";
//        }
//        w << "[" << name << "];";
//        return w.str();
//    }
//    video_track source;
//    uint64_t offset_ms;
//};
//
//class stacked_block : public block {
//public:
//    stacked_block(const std::string &name, bool is_vertical)
//            : block(name, dimension(0, 0)), is_vertical(is_vertical) {}
//
//    void stack(block* block) {
//        if (is_vertical) {
//            dim.h += block->dim.h;
//            if (dim.w == 0) {
//                dim.w = block->dim.w;
//            } else {
//                assert(dim.w == block->dim.w);
//            }
//        } else {
//            dim.w += block->dim.w;
//            if (dim.h == 0) {
//                dim.h = block->dim.h;
//            } else {
//                assert(dim.h == block->dim.h);
//            }
//        }
//        blocks.emplace_back(block);
//    }
//
//private:
//    std::string _filter() const override {
//        std::ostringstream w1;
//        std::ostringstream w2;
//        for (auto& b : blocks) {
//            w1 << b->filter();
//            w2 << "[" << b->name << "]";
//        }
//        w1 << w2.str();
//        if (blocks.size() > 1) {
//            w1 << (is_vertical ? "vstack" : "hstack");
//            if (blocks.size() != 2) {
//                w1 << "=inputs=" << blocks.size();
//            }
//            w1 << ",fifo";
//        } else {
//            w1 << "fifo";
//        }
//
//        if (!name.empty()) {
//            w1 << "[" << name << "];";
//        }
//        return w1.str();
//    }
//
//    std::list<std::unique_ptr<block>> blocks;
//    bool is_vertical;
//};
//
//class pad_block : public block {
//public:
//    pad_block(const std::string& name, const dimension& dim)
//            : block(name, dim) {}
//private:
//    std::string _filter() const override {
//        std::ostringstream w;
//        w << "color=color=Black:size=" << dim.w << "x" << dim.h << ":d=0.000001[" << name << "];";
//        return w.str();
//    }
//};

// - scale
//class source_block : public block {
//public:
//    source_block(const video_track& source, const dimension& dim, const std::string& name, uint64_t begin_ts)
//            : block(name, dim, source.duration_ms), source(source), begin_ts(begin_ts) {}
//private:
//    virtual std::string _filter() const {
//        std::ostringstream w;
//        auto offset_ms = source.begin_ts - begin_ts;
//        if (offset_ms) {
//            w << "color=color=Black:size=" << dim.w << "x" << dim.h;
//            w << ":d=" << (offset_ms / 1000) << "." << (offset_ms % 1000);
//            w << ",fifo";
//            w << "[" << name << "-offset];";
//        }
//        // source
//        w << "[" << source.input_id << ":" << source.id << "]";
//        // w << "scale=w='iw*min(" << dim.w << "/iw," << dim.h << "/ih)':h='ih*min(" << dim.w << "/iw," << dim.w << "/ih),'";
//        w << "fifo";
//        w << "[" << name << "-source];";
//        if (offset_ms) {
//            w << "[" << name << "-offset][" << name << "-source]" << "concat,fifo[" << name << "];";
//        } else {
//            w << "[" << name << "-source]null[" << name << "];";
//        }
//        return w.str();
//    }
//    video_track source;
//    uint64_t begin_ts;
//};

class filter_block : public block {
public:
    filter_block(const std::string& filter_string, const dimension& dim, const std::string& name, uint64_t begin_ts, uint64_t duration_ms)
            : block(name, dim, duration_ms), _filter_string(filter_string), _begin_ts(begin_ts) {}
private:
    virtual std::string _filter(uint64_t base_begin_ts) const override {
        std::ostringstream w;
        auto offset_ms = _begin_ts - base_begin_ts;
        if (offset_ms) {
            w << "color=color=Black:size=" << dim.w << "x" << dim.h;
            w << ":d=" << (offset_ms / 1000) << "." << (offset_ms % 1000);
            w << ",fifo";
            w << "[" << name << "-hd];";
            w << _filter_string << "[" << name << "-bd];";
            w << "[" << name << "-hd][" << name << "-bd]" << "concat,fifo[" << name << "];";
        } else {
            w << _filter_string << "[" << name << "];";
        }
        return std::string(w.str());
    }
    std::string _filter_string;
    uint64_t _begin_ts;
};

class background_block : public block {
private:
    class block_overlay {
    public:
        block_overlay(const filter_block& block, int origin_x, int origin_y) : block(block), origin_x(origin_x), origin_y(origin_y) {}
        filter_block block;
        int origin_x;
        int origin_y;
    };
public:
    background_block(const dimension& dim, const std::string& name, uint64_t duration_ms)
            : block(name, dim, duration_ms) {}

    void overlay(const filter_block& block, int origin_x, int origin_y) {
        overlays_.emplace_back(std::move(block_overlay(block, origin_x, origin_y)));
        int end_x = origin_x + block.dim.w;
        int end_y = origin_y + block.dim.h;
        if (end_x > dim.w) {
            dim.w = end_x;
        }
        if (end_y > dim.h) {
            dim.h = end_y;
        }
    }
private:
    std::string _filter(uint64_t base_begin_ts) const override {
        std::ostringstream overlays;
        std::ostringstream sources;
        sources << "color=color=Black:size=" << dim.w << "x" << dim.h;
        sources << ":d=" << (duration_ms / 1000) << "." << (duration_ms % 1000);
        sources << ",format=pix_fmts=yuv420p";
        sources << "[" << name << "];";
        std::string bg_name = name;
        for (int i = 0; i < overlays_.size(); ++i) {
            auto& o = overlays_[i];
            std::string block_filter = o.block.filter(base_begin_ts);
            sources << block_filter;
            overlays << "[" << bg_name << "][" << o.block.name << "]";
            overlays << "overlay=x='" << o.origin_x << "+(" << o.block.dim.w << "-w)/2':y='" << o.origin_y << "+(" << o.block.dim.h << "-h)/2':eof_action=pass";
            bg_name = bg_name + "+" + o.block.name;
            if (i != overlays_.size() - 1) {
                overlays << "[" << bg_name << "];";
            }
        }
        sources << overlays.str();
        return std::string(sources.str());
    }

    std::vector<block_overlay> overlays_;
};

#endif //MERGE_BLOCK_HPP
